<template>
  <div
    class="mpd-scroll-bar"
    v-if="showBar"
    :class="[verticalAndHorizontal]"
    :style="[{ [position]: 0 }]"
    ref="container"
    @wheel.passive="wheelHandler"
  >
    <div
      class="mpd-scroll-bar-thumb"
      ref="thumb"
      :style="[{ [config.sizeAttr]: barSize + 'px' }, translate]"
      @mousedown.stop="mouseDownHandler"
    ></div>
  </div>
</template>

<script>
import { addEvent, removeEvent } from '@/util/event'
import { positionArrays, barConfigs, verticalOrHorizontal } from './constant'

export default {
  name: 'ScrollThumb',
  props: {
    // 滚动条显示的方位
    position: {
      type: String,
      validator: (value) => positionArrays.indexOf(value) !== -1
    },
    // 滚轮偏移量
    wheel: {
      type: Number,
      default: 0
    },
    // 父元素的view改变大小的时候改变该值
    listenerResize: {
      type: Boolean
    }
  },
  data() {
    return {
      // 当前滚动条配置,主要是存横向或者纵向的配置
      config: {},
      // 父元素的视图dom对象
      parentView: null,
      // 父元素的容器dom对象
      parentBody: null,
      // 滚动条大小
      barSize: 0,
      // 标记鼠标按下后开始移动的状态,鼠标按下为true抬起为false
      startMove: false,
      // 鼠标按下开始的位置
      mouseAxis: 0,
      // 滚动条偏移量
      barOffset: 0,
      // 滚动条最大的偏移量
      barMaxOffset: 0,
      // 滚动条空白区域与(内容元素的高度-视图区域的高度)的比例
      percentage: 0
    }
  },
  computed: {
    // 判断是横向还是纵向
    verticalAndHorizontal() {
      return verticalOrHorizontal(this.position)
    },
    // 滚动条的偏移量
    translate() {
      return {
        transform: `${this.config.translate}(${this.barOffset}px)`
      }
    },
    // 判断是否显示bar
    showBar() {
      return this.percentage !== 1
    }
  },
  watch: {
    wheel(val, oldVal) {
      // 滚轮滑动的时候改变滚动条的偏移量
      this.changeBarOffset(this.barOffset + (val - oldVal) * this.percentage)
    },
    // 父元素view改变大小时重新计算滚动条的偏移量
    listenerResize() {
      this.computeScrollBarSize()
      this.changeBarOffset(this.parentBody[this.config.offset] * this.percentage)
    }
  },
  methods: {
    /**
     * 计算滚动条大小
     */
    computeScrollBarSize() {
      const viewSize = this.parentView.getBoundingClientRect()[this.config.sizeAttr]
      const bodySize = this.parentBody.getBoundingClientRect()[this.config.sizeAttr]
      this.percentage = bodySize / viewSize
      this.barSize = this.percentage * bodySize
      this.barMaxOffset = bodySize - this.barSize
    },
    /**
     * 鼠标按下滚动条事件
     */
    mouseDownHandler(event) {
      // 只有鼠标左键可以拖动
      if (event.button !== 0) {
        return
      }
      this.stopEvent(event)
      // 标记开始拖拽滚动条
      this.startMove = true
      // 记录鼠标起始的位置
      this.mouseAxis = event[this.config.clientAxis]
      // 给document绑定 mouseup与mousemove
      this.bindBarEvent()
    },
    /**
     * 鼠标移动事件
     */
    mouseMoveHandler(event) {
      this.stopEvent(event)
      // 相对于滚动条容器的offset
      const thumb = this.$refs.thumb.getBoundingClientRect()
      const container = this.$refs.container.getBoundingClientRect()
      const contrastParentOffset = thumb[this.config.direction1] - container[this.config.direction1]
      let nextAxis = event[this.config.clientAxis]
      if (nextAxis < container[this.config.direction1]) {
        nextAxis = container[this.config.direction1]
      } else if (nextAxis > container[this.config.direction2]) {
        nextAxis = container[this.config.direction2]
      }
      /**
       * offset = 鼠标移动的偏移量 + 滚动条当前的偏移量
       * offset为滚动条需要移动到的位置
       */
      const offset = nextAxis - this.mouseAxis + contrastParentOffset
      this.mouseAxis = nextAxis
      this.changeBarOffset(offset)
    },
    /**
     * 改变滚动条偏移量
     */
    changeBarOffset(offset) {
      // 防止滚动条越界
      if (offset < 0) {
        offset = 0
      }
      if (offset > this.barMaxOffset) {
        offset = this.barMaxOffset
      }
      this.barOffset = offset
      this.parentBody[this.config.offset] = this.barOffset / this.percentage
    },
    /**
     * 鼠标抬起事件
     */
    mouseUpHandler() {
      // 结束拖拽状态
      this.startMove = false
      removeEvent(document, 'mouseup', this.mouseUpHandler)
      removeEvent(document, 'mousemove', this.mouseMoveHandler)
    },
    /**
     * 绑定滚动条事件
     */
    bindBarEvent() {
      addEvent(document, 'mouseup', this.mouseUpHandler)
      addEvent(document, 'mousemove', this.mouseMoveHandler)
    },
    /**
     * 阻止浏览器事件
     */
    stopEvent(event) {
      event.preventDefault()
      event.stopPropagation()
      event.stopImmediatePropagation()
    },
    /**
     * 鼠标滑轮事件
     */
    wheelHandler(event) {
      // 相对于滚动条容器的offset
      const thumb = this.$refs.thumb.getBoundingClientRect()
      const container = this.$refs.container.getBoundingClientRect()
      const contrastParentOffset = thumb[this.config.direction1] - container[this.config.direction1]
      // 滚动条最终要设置的偏移量    event[this.config.wheelDelta] => 获取鼠标滚轮的滚动值
      const offset = contrastParentOffset + event[this.config.wheelDelta]
      this.changeBarOffset(offset)
    }
  },
  mounted() {
    // 获取父元素的view和body的dom对象
    this.parentView = this.$parent.$refs.view
    this.parentBody = this.$parent.$refs.body
    this.config = barConfigs[verticalOrHorizontal(this.position)]
    this.computeScrollBarSize()
  },
  beforeDestroy() {
    this.mouseUpHandler()
  }
}
</script>
